package cn.axj.crypt.handler;


import cn.axj.crypt.annotation.Decryption;
import com.alibaba.fastjson.JSONObject;
import cn.axj.crypt.config.CryptProperties;
import cn.axj.crypt.exception.BindingException;
import cn.axj.crypt.exception.DecryptErrorException;
import cn.axj.crypt.exception.EncryptionStringNameIsNullException;
import cn.axj.crypt.exception.ParameterBodyIsNullException;
import cn.axj.crypt.secret.BaseAlgorithmHttpInputMessage;
import org.apache.commons.io.IOUtils;
import org.springframework.core.MethodParameter;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.servlet.mvc.method.annotation.RequestBodyAdvice;

import javax.annotation.Resource;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;

/**
 * @author aoxiaojun
 * @date 2020/12/10 17:21
 **/
@ControllerAdvice
public class ParameterDecryptionAdvice implements RequestBodyAdvice {

    @Resource
    private CryptProperties cryptProperties;

    private List<BaseAlgorithmHttpInputMessage> decryptionList = new ArrayList<>(8);

    public ParameterDecryptionAdvice(List<BaseAlgorithmHttpInputMessage> list) throws BindingException {
        init(list);
    }

    private void init(List<BaseAlgorithmHttpInputMessage> list) throws BindingException {
        String temp;
        for(int i=0;i<list.size();i++){
            temp = list.get(i).getBinding();
            for (int j = i+1;j<list.size();j++){
                String binding = list.get(j).getBinding();
                if(temp.equals(binding)){
                    throw new BindingException("The algorithm implements binding value is repeat!please check it exists two value of " + binding);
                }
            }
        }
        this.decryptionList.addAll(list);
    }

    @Override
    public boolean supports(MethodParameter methodParameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
        if(!cryptProperties.isParameterDecryption()){
            return false;
        }
        Decryption decryption = methodParameter.getParameterAnnotation(Decryption.class);
        RequestBody requestBody = methodParameter.getParameterAnnotation(RequestBody.class);
        return Objects.nonNull(decryption) && Objects.nonNull(requestBody);
    }

    @Override
    public HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) throws RuntimeException, IOException {
        Decryption decryption = parameter.getParameterAnnotation(Decryption.class);
        assert decryption != null;
        String simpleName = parameter.getContainingClass().getSimpleName();
        String methodName = Objects.requireNonNull(parameter.getMethod()).getName();

        String bind = decryption.bind();
        String encryptionStringName = decryption.encryptionStringName();
        if(StringUtils.isEmpty(encryptionStringName)){
            throw new EncryptionStringNameIsNullException("The annotation Decryption‘s encryptionStringName is empty,please check the method annotation,target method is "+simpleName+"."+methodName);
        }

        InputStream body = inputMessage.getBody();
        if(body == null){
            throw new ParameterBodyIsNullException("request body is null");
        }

        for (BaseAlgorithmHttpInputMessage baseDecryption : decryptionList) {
            if (baseDecryption.getBinding().equals(bind)) {
                String encryptData = IOUtils.toString(body, cryptProperties.getCharset());
                JSONObject jsonObject = JSONObject.parseObject(encryptData);
                String encryptString = jsonObject.getString(encryptionStringName);
                if(StringUtils.isEmpty(encryptString)){
                    throw new ParameterBodyIsNullException("request body is null");
                }
                String decrypt;
                try {
                    decrypt = baseDecryption.decrypt(encryptString);
                }catch (Exception e) {
                    e.printStackTrace();
                    throw new DecryptErrorException("decrypt error for String " + encryptString+" ,current algorithm is "+ bind);
                }
                InputStream inputStream = IOUtils.toInputStream(decrypt, cryptProperties.getCharset());
                try {
                    BaseAlgorithmHttpInputMessage baseAlgorithmHttpInputMessage = baseDecryption.getClass().newInstance();
                    baseAlgorithmHttpInputMessage.setHeaders(inputMessage.getHeaders());
                    baseAlgorithmHttpInputMessage.setBody(inputStream);
                    return baseAlgorithmHttpInputMessage;
                }catch (Exception e){
                    e.printStackTrace();
                }
            }
        }
        throw new BindingException("Unable to find a binding object of decryption,targetClass is "+simpleName+",method is "+methodName);
    }

    @Override
    public Object afterBodyRead(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
        return body;
    }

    @Override
    public Object handleEmptyBody(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
        return body;
    }
}
